package com.tenbent.product.base.config.security;

import com.tenbent.product.base.config.datasource.DataSourceConfig;
import com.tenbent.product.base.security.*;
import com.tenbent.product.base.security.impl.CustomAccessDecisionManagerImpl;
import com.tenbent.product.base.security.impl.CustomAuthenticationFailureHandlerImpl;
import com.tenbent.product.base.security.impl.CustomAuthenticationSuccessHandlerImpl;
import com.tenbent.product.base.security.impl.CustomFilterInvocationSecurityMetadataSourceImpl;
import com.tenbent.product.center.user.service.UserService;
import com.tenbent.product.center.user.service.impl.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;

import javax.sql.DataSource;

/**
 * 安全配置类(security)
 *
 * @author Randy
 *
 *         Created by ThinkPad on 2017/8/31.
 */
@Configuration
@EnableWebSecurity
// 开启方法级认证 只针对非controller层的方法级
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Import({ DataSourceConfig.class })
@Order(200)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	private static final String REALM = "MY_TEN_BENT_REALM";

	public static final String USER_SQL = "SELECT login_id , password,true FROM pc_user WHERE login_id = ?";

	public static final String AUTHORITIES_SQL = "SELECT u.login_id, r.role_code FROM pc_role r LEFT JOIN pc_user_role ur ON r.id = ur.role_id LEFT JOIN pc_user u ON ur.user_id = u.id WHERE u.login_id = ?";

	@Value("${security.access.denied.page}")
	private String accessDeniedPage;
	@Value("${security.login.page}")
	private String loginPage;
	@Value("${security.login.processing.url}")
	private String loginProcessingUrl;
	@Value("${security.username.parameter}")
	private String usernameParameter;
	@Value("${security.password.parameter}")
	private String passwordParameter;
	@Value("${security.rememberme.key}")
	private String rememberMeKey;
	@Value("${security.rememberme.parameter}")
	private String rememberMeParameter;
	@Value("${security.logout.success.url}")
	private String logoutSuccessUrl;
	@Value("${security.session.expired.url}")
	private String expiredUrl;

	@Autowired
	private DataSource dataSource;

	// 配置及产生记住我token存放cookie
	@Bean
	public PersistentTokenRepository persistentTokenRepository() {

		/**
		 *
		 * 要使用记住我，数据库需要表 persistent_logins
		 *
		 * CREATE TABLE persistent_logins ( username VARCHAR(64) NOT NULL,
		 * series VARCHAR(64) NOT NULL, token VARCHAR(64) NOT NULL, last_used
		 * TIMESTAMP NOT NULL, PRIMARY KEY (series) );
		 *
		 */
		JdbcTokenRepositoryImpl tokenRepositoryImpl = new JdbcTokenRepositoryImpl();
		tokenRepositoryImpl.setDataSource(dataSource);
		return tokenRepositoryImpl;
	}

	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}

	@Bean
	public CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler() {
		return new CustomAuthenticationSuccessHandlerImpl();
	}

	@Bean
	public CustomAuthenticationFailureHandler customAuthenticationFailureHandler() {
		return new CustomAuthenticationFailureHandlerImpl();
	}

	@Bean
	public BasicAuthenticationEntryPoint getBasicAuthEntryPoint() {
		return new CustomBasicAuthenticationEntryPoint();
	}

	// TODO 此处不够优雅一般上层结构不应依赖下层结构,暂时没想到好办法
	@Bean
	public UserService userDetailsService() {
		return new UserServiceImpl();
	}

	// 配置认证驱动满足加密算法匹配密码
	@Bean
	public DaoAuthenticationProvider authenticationProvider() {
		DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
		authenticationProvider.setUserDetailsService(userDetailsService());
		authenticationProvider.setPasswordEncoder(passwordEncoder());
		return authenticationProvider;
	}

	// 开启全局方法级安全需要此 AuthenticationManager
	@Bean
	@Override
	public AuthenticationManager authenticationManagerBean() throws Exception {
		return super.authenticationManagerBean();
	}

	@Bean
	public FilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource() {
		return new CustomFilterInvocationSecurityMetadataSourceImpl();
	}

	@Bean
	public CustomAccessDecisionManager customAccessDecisionManager() {
		return new CustomAccessDecisionManagerImpl();
	}

	@Override
	public void configure(AuthenticationManagerBuilder auth) throws Exception {
		// // 内存方式配置用户存储(方式一)
		// auth.inMemoryAuthentication().withUser("yan").password("123456").roles("USER");
		// auth.inMemoryAuthentication().withUser("admin").password("123456").roles("ADMIN");
		// auth.inMemoryAuthentication().withUser("dba").password("123456").roles("DBA");

		// // 数据库方式配置用户存储(自定义扩展 UserDetailsService 接口 类型 推荐方式)
		auth.userDetailsService(userDetailsService());
		// 配置密码加密
		auth.authenticationProvider(authenticationProvider());

		// （类型二）数据库方式配置用户存储(SQl 查询类型 必须满足security的 字段方式)
		// auth.jdbcAuthentication().dataSource(dataSource).usersByUsernameQuery(USER_SQL)
		// .authoritiesByUsernameQuery(AUTHORITIES_SQL).rolePrefix("ROLE_");
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {

		// *********************** 动态web方式 ***************************************//
		http.csrf().disable().authorizeRequests()
				.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
					public <O extends FilterSecurityInterceptor> O postProcess(O fsi) {
						fsi.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource());
						fsi.setAccessDecisionManager(customAccessDecisionManager());
						return fsi;
					}
				})
				.anyRequest().permitAll()
				.and()
					.exceptionHandling().accessDeniedPage(accessDeniedPage)
				.and()
					.formLogin().loginPage(loginPage).loginProcessingUrl(loginProcessingUrl)
					.successHandler(customAuthenticationSuccessHandler())
					.failureHandler(customAuthenticationFailureHandler()).usernameParameter(usernameParameter)
					.passwordParameter(passwordParameter)
				.and()
					.rememberMe().key(rememberMeKey)
					.rememberMeParameter(rememberMeParameter).tokenRepository(persistentTokenRepository())
					.tokenValiditySeconds(60)
				.and().logout().deleteCookies().logoutSuccessUrl(logoutSuccessUrl)
				.and()
					// 配置session
					.sessionManagement().maximumSessions(1).expiredUrl(expiredUrl);
		// 设置可以iframe访问
		http.headers().frameOptions().sameOrigin();
		// *********************** 动态web方式 ***************************************//

		// // *********************** 普通web方式 ***************************************//
		// // 配置web安全方式
		// http.csrf().disable().authorizeRequests()
		// .antMatchers("/eg/home/**").access("hasRole('ROLE_USER') or
		// hasRole('ROLE_ADMIN') or hasRole('ROLE_DBA')")
		// .antMatchers("/eg/hello/**").access("hasRole('ROLE_USER')")
		// .antMatchers("/eg/admin/**").access("hasRole('ROLE_ADMIN')")
		// .antMatchers("/eg/dba/**").access("hasRole('ROLE_ADMIN') or
		// hasRole('ROLE_DBA')")
		// // .antMatchers("/api/eg/**").authenticated()
		// .anyRequest().permitAll()
		// .and()
		// .exceptionHandling().accessDeniedPage("/eg/denied")
		// .and()
		// // 说明: loginPage("/login") 指Controller的login方法
		// // 说明：loginProcessingUrl("/login") 指登录页面form表单的action的值
		// .formLogin().loginPage("/login").loginProcessingUrl("/login")
		// // 说明：如果 defaultSuccessUrl 和 successHandler
		// // 都配置了，优先级以配置顺序后面的配置的为主，建议两者不要同时配置
		// .defaultSuccessUrl("/eg/home")
		// // .successHandler(customAuthenticationSuccessHandler())
		// // 说明：如果 failureUrl 和 failureHandler
		// // 都配置了，优先级以配置顺序后面的配置的为主，建议两者不要同时配置
		// // .failureUrl("/login?error")
		// .failureHandler(customAuthenticationFailureHandler())
		// .usernameParameter("username")
		// .passwordParameter("password")
		// .and()
		// .rememberMe().key("security-demo")
		// .rememberMeParameter("remember-me").tokenRepository(persistentTokenRepository())
		// .tokenValiditySeconds(60).and().logout().deleteCookies().logoutSuccessUrl("/login?logout").and()
		// // 配置session
		// .sessionManagement().maximumSessions(1).expiredUrl("/login?error=expired");
		// // 设置可以iframe访问
		// http.headers().frameOptions().sameOrigin();
		// // *********************** 普通web方式 ***************************************//

		/**
		 * 说明：开启基本身份认证(非token方式)
		 * 普通web方式与RestApi方式不可同一个项目同时存在，因为普通web需要配置session，RestApi不需要配置session。
		 *
		 * 基本身份认证需要http请求头存在base64客户端凭证 Authorization=用户ID:密码
		 *
		 */
		// // *********************** restFul (不含token)方式 ***************************************//
		// // 配置restApi方式
		// http.csrf().disable()
		// .authorizeRequests()
		// .antMatchers("/api/eg/**").access("hasRole('ROLE_ADMIN')")
		// .anyRequest().permitAll()
		// .and()
		// .httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthEntryPoint())
		// .and()
		// .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
		// // *********************** restFul 方式 ***************************************//
	}

	@Override
	public void configure(WebSecurity web) throws Exception {
		// 设置不拦截规则
		web.ignoring().antMatchers("/druid/**");
	}

	// public static void main(String[] args) {
	// String password = "123456";
	// BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
	// System.out.println("password = " + passwordEncoder.encode(password));
	// // 生成密码是： $2a$10$9a86qccwoHI.MCX03wFg..lM67bUIjZRWmggJEbCg0MqgN.WT/mBG
	// }
}
